import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_nfc_kit/flutter_nfc_kit.dart';
import 'package:ndef/ndef.dart' as ndef;

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  group('FlutterNfcKit', () {
    const MethodChannel channel = MethodChannel('flutter_nfc_kit/method');
    const EventChannel eventChannel = EventChannel('flutter_nfc_kit/event');
    
    setUp(() {
      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
        channel,
        null,
      );
      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockStreamHandler(
        eventChannel,
        null,
      );
    });

    tearDown(() {
      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
        channel,
        null,
      );
      TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockStreamHandler(
        eventChannel,
        null,
      );
    });

    group('NFC Availability', () {
      test('should return not_supported when NFC is not supported', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'getNFCAvailability') {
              return 'not_supported';
            }
            return null;
          },
        );

        final availability = await FlutterNfcKit.nfcAvailability;
        expect(availability, NFCAvailability.not_supported);
      });

      test('should return disabled when NFC is disabled', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'getNFCAvailability') {
              return 'disabled';
            }
            return null;
          },
        );

        final availability = await FlutterNfcKit.nfcAvailability;
        expect(availability, NFCAvailability.disabled);
      });

      test('should return available when NFC is available', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'getNFCAvailability') {
              return 'available';
            }
            return null;
          },
        );

        final availability = await FlutterNfcKit.nfcAvailability;
        expect(availability, NFCAvailability.available);
      });
    });

    group('Tag Polling', () {
      const String mockTagJson = '''
      {
        "type": "iso7816",
        "id": "04:12:34:56:78:90:AB",
        "standard": "ISO 14443-4 (Type A)",
        "atqa": "0400",
        "sak": "20",
        "historicalBytes": "",
        "protocolInfo": "",
        "applicationData": "",
        "hiLayerResponse": "",
        "manufacturer": "",
        "systemCode": "",
        "dsfId": "",
        "ndefAvailable": true,
        "ndefType": "Type 4",
        "ndefCapacity": 8192,
        "ndefWritable": true,
        "ndefCanMakeReadOnly": false,
        "webUSBCustomProbeData": "",
        "mifareInfo": null
      }
      ''';

      test('should poll NFC tag successfully', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'poll') {
              expect(methodCall.arguments['timeout'], 20000);
              expect(methodCall.arguments['technologies'], 0xB); // ISO14443A + ISO14443B + ISO15693
              return mockTagJson;
            }
            return null;
          },
        );

        final tag = await FlutterNfcKit.poll();
        expect(tag.type, NFCTagType.iso7816);
        expect(tag.id, "04:12:34:56:78:90:AB");
        expect(tag.standard, "ISO 14443-4 (Type A)");
        expect(tag.ndefAvailable, true);
        expect(tag.ndefCapacity, 8192);
      });

      test('should poll with custom timeout', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'poll') {
              expect(methodCall.arguments['timeout'], 5000);
              return mockTagJson;
            }
            return null;
          },
        );

        await FlutterNfcKit.poll(timeout: const Duration(seconds: 5));
      });

      test('should poll with custom technology flags', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'poll') {
              expect(methodCall.arguments['technologies'], 0x5); // ISO14443A + ISO18092
              return mockTagJson;
            }
            return null;
          },
        );

        await FlutterNfcKit.poll(
          readIso14443A: true,
          readIso14443B: false,
          readIso18092: true,
          readIso15693: false,
        );
      });

      test('should poll with Android options', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'poll') {
              expect(methodCall.arguments['technologies'], 0x18B); // Default flags + no sound + no NDEF check
              return mockTagJson;
            }
            return null;
          },
        );

        await FlutterNfcKit.poll(
          androidPlatformSound: false,
          androidCheckNDEF: false,
        );
      });
    });

    group('Tag Stream', () {
      test('should provide tag stream interface', () {
        // 测试流接口的存在性
        final tagStream = FlutterNfcKit.tagStream;
        expect(tagStream, isA<Stream<NFCTag>>());
      });
    });

    group('NDEF Operations', () {
      test('should read NDEF records', () async {
        const String mockNdefJson = '''
        [
          {
            "identifier": "",
            "payload": "48656C6C6F20576F726C64",
            "type": "54",
            "typeNameFormat": "nfcWellKnown"
          }
        ]
        ''';

        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'readNDEF') {
              expect(methodCall.arguments['cached'], false);
              return mockNdefJson;
            }
            return null;
          },
        );

        final records = await FlutterNfcKit.readNDEFRawRecords();
        expect(records.length, 1);
        expect(records[0].payload, "48656C6C6F20576F726C64");
        expect(records[0].typeNameFormat, ndef.TypeNameFormat.nfcWellKnown);
      });

      test('should read NDEF records with cache', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'readNDEF') {
              expect(methodCall.arguments['cached'], true);
              return '[]';
            }
            return null;
          },
        );

        await FlutterNfcKit.readNDEFRawRecords(cached: true);
      });

      test('should write NDEF records', () async {
        final records = [
          NDEFRawRecord(
            "",
            "48656C6C6F20576F726C64",
            "54",
            ndef.TypeNameFormat.nfcWellKnown,
          ),
        ];

        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'writeNDEF') {
              final data = methodCall.arguments['data'] as String;
              expect(data.contains('"payload":"48656C6C6F20576F726C64"'), true);
              return null;
            }
            return null;
          },
        );

        await FlutterNfcKit.writeNDEFRawRecords(records);
      });

      test('should make NDEF readonly', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'makeNdefReadOnly') {
              return null;
            }
            return null;
          },
        );

        await FlutterNfcKit.makeNdefReadOnly();
      });
    });

    group('MIFARE Operations', () {
      test('should authenticate sector with keyA', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'authenticateSector') {
              expect(methodCall.arguments['index'], 1);
              expect(methodCall.arguments['keyA'], "FFFFFFFFFFFF");
              expect(methodCall.arguments['keyB'], null);
              return true;
            }
            return null;
          },
        );

        final result = await FlutterNfcKit.authenticateSector<String>(
          1,
          keyA: "FFFFFFFFFFFF",
        );
        expect(result, true);
      });

      test('should authenticate sector with keyB', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'authenticateSector') {
              expect(methodCall.arguments['index'], 2);
              expect(methodCall.arguments['keyA'], null);
              expect(methodCall.arguments['keyB'], "000000000000");
              return false;
            }
            return null;
          },
        );

        final result = await FlutterNfcKit.authenticateSector<String>(
          2,
          keyB: "000000000000",
        );
        expect(result, false);
      });

      test('should read block', () async {
        final mockData = Uint8List.fromList([0x01, 0x02, 0x03, 0x04]);

        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'readBlock') {
              expect(methodCall.arguments['index'], 4);
              return mockData;
            }
            return null;
          },
        );

        final data = await FlutterNfcKit.readBlock(4);
        expect(data, mockData);
      });

      test('should write block', () async {
        final testData = Uint8List.fromList([0xFF, 0xFF, 0xFF, 0xFF]);

        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'writeBlock') {
              expect(methodCall.arguments['index'], 5);
              expect(methodCall.arguments['data'], testData);
              return null;
            }
            return null;
          },
        );

        await FlutterNfcKit.writeBlock<Uint8List>(5, testData);
      });

      test('should read sector', () async {
        final mockSectorData = Uint8List.fromList(List.generate(64, (i) => i));

        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'readSector') {
              expect(methodCall.arguments['index'], 3);
              return mockSectorData;
            }
            return null;
          },
        );

        final data = await FlutterNfcKit.readSector(3);
        expect(data.length, 64);
        expect(data[0], 0);
        expect(data[63], 63);
      });
    });

    group('Transceive Operations', () {
      test('should transceive with hex string', () async {
        const String command = "00A4040007A0000002471001";
        const String response = "9000";

        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'transceive') {
              expect(methodCall.arguments['data'], command);
              expect(methodCall.arguments['timeout'], 5000);
              return response;
            }
            return null;
          },
        );

        final result = await FlutterNfcKit.transceive<String>(command);
        expect(result, response);
      });

      test('should transceive with byte array', () async {
        final command = Uint8List.fromList([0x00, 0xA4, 0x04, 0x00]);
        final response = Uint8List.fromList([0x90, 0x00]);

        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'transceive') {
              expect(methodCall.arguments['data'], command);
              return response;
            }
            return null;
          },
        );

        final result = await FlutterNfcKit.transceive<Uint8List>(command);
        expect(result, response);
      });

      test('should transceive with custom timeout', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'transceive') {
              expect(methodCall.arguments['timeout'], 10000);
              return "9000";
            }
            return null;
          },
        );

        await FlutterNfcKit.transceive<String>(
          "00A4040007A0000002471001",
          timeout: const Duration(seconds: 10),
        );
      });
    });

    group('Session Management', () {
      test('should finish session', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'finish') {
              expect(methodCall.arguments['closeWebUSB'], false);
              return null;
            }
            return null;
          },
        );

        await FlutterNfcKit.finish();
      });

      test('should finish session with iOS messages', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'finish') {
              expect(methodCall.arguments['iosAlertMessage'], "Success!");
              expect(methodCall.arguments['iosErrorMessage'], null);
              return null;
            }
            return null;
          },
        );

        await FlutterNfcKit.finish(iosAlertMessage: "Success!");
      });

      test('should finish session with WebUSB close', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'finish') {
              expect(methodCall.arguments['closeWebUSB'], true);
              return null;
            }
            return null;
          },
        );

        await FlutterNfcKit.finish(closeWebUSB: true);
      });

      test('should restart iOS polling', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'restartPolling') {
              return null;
            }
            return null;
          },
        );

        await FlutterNfcKit.iosRestartPolling();
      });

      test('should set iOS alert message', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'setIosAlertMessage') {
              expect(methodCall.arguments, "Scan your card");
              return null;
            }
            return null;
          },
        );

        await FlutterNfcKit.setIosAlertMessage("Scan your card");
      });
    });

    group('Error Handling', () {
      test('should handle platform exception during poll', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'poll') {
              throw PlatformException(
                code: '404',
                message: 'NFC not available',
                details: null,
              );
            }
            return null;
          },
        );

        expect(
          () async => await FlutterNfcKit.poll(),
          throwsA(isA<PlatformException>()),
        );
      });

      test('should handle platform exception during transceive', () async {
        TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
          channel,
          (MethodCall methodCall) async {
            if (methodCall.method == 'transceive') {
              throw PlatformException(
                code: '406',
                message: 'No tag polled',
                details: null,
              );
            }
            return null;
          },
        );

        expect(
          () async => await FlutterNfcKit.transceive<String>("00A4040007A0000002471001"),
          throwsA(isA<PlatformException>()),
        );
      });
    });
  });
}